package com.osce.score.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.csce.domain.entity.score.*;
import com.csce.domain.entity.score.excel.ExamExaminer;
import com.csce.domain.entity.score.excel.ExamSimulatedPatient;
import com.csce.domain.entity.score.excel.ExamStudent;
import com.csce.domain.entity.score.vo.ExamAllotResult;
import com.doudou.core.container.ListUtil;
import com.doudou.core.container.MapUtil;
import com.doudou.core.container.SetUtil;
import com.doudou.core.date.DateUtil;
import com.doudou.core.file.FileUtil;
import com.doudou.core.lang.ObjUtil;
import com.doudou.core.lang.Option;
import com.doudou.core.random.UUIDUtil;
import com.doudou.core.str.StrUtil;
import com.doudou.core.throwable.GlobalException;
import com.douodu.excel.hutool.HutoExcelUtil;
import com.douodu.excel.jxls.JxlsExcel;
import com.osce.common.constant.AppValues;
import com.osce.common.constant.CacheConstants;
import com.osce.common.utils.DateUtils;
import com.osce.common.utils.DictUtils;
import com.osce.common.utils.oesc.*;
import com.osce.interfaces.basic.IBasicExPlaceService;
import com.osce.interfaces.score.*;
import com.osce.interfaces.system.ISysUserService;
import com.osce.score.mapper.ScoreExExamMapper;
import com.osce.score.service.socket.ExamRedisService;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 考试Service业务层处理
 *
 * @author zzx
 * @since 2023-09-05
 */
@Service
public class ScoreExExamServiceImpl extends ServiceImpl<ScoreExExamMapper, ScoreExExam> implements IScoreExExamService {
    private static final Log log = LogFactory.get(ScoreExExamServiceImpl.class);

    @Resource
    private IScoreExExamTimeService timeService;
    @Resource
    private IScoreExExamPlaceService placeService;
    @Resource
    private IScoreExExamSessionService sessionService;
    @Resource
    private IScoreExExamPlaceAllotService placeAllotService;
    @Resource
    private IBasicExPlaceService basicExPlaceService;
    @Resource
    private IScoreSkillService skillService;
    @Resource
    private IScoreSheetService sheetService;
    @Resource
    private IScoreExExamStudentService studentService;
    @Resource
    private ISysUserService userService;
    @Resource
    private AppValues appValues;
    @Resource
    private ExamRedisService redisService;

    /**
     * 查询考试
     *
     * @param id 考试主键
     * @return 考试
     */
    @Override
    public ScoreExExam selectScoreExExamById(Long id) {
        return checkExamStatusByDate(baseMapper.selectScoreExExamById(id));
    }

    /**
     * 查询考试列表
     *
     * @param scoreExExam 考试
     * @return 考试
     */
    @Override
    public List<ScoreExExam> selectScoreExExamList(ScoreExExam scoreExExam) {
        List<ScoreExExam> scoreExExams = baseMapper.selectScoreExExamList(scoreExExam);
        scoreExExams.forEach(this::checkExamStatusByDate);
        return scoreExExams;
    }

    /**
     * 新增考试
     *
     * @param scoreExExam 考试
     * @return 结果
     */
    @Override
    @Transactional
    public int insertScoreExExam(ScoreExExam scoreExExam) {
        checkExamDate(scoreExExam);
        scoreExExam.setCreateTime(DateUtils.getNowDate());
        scoreExExam.setStatus(1);
        scoreExExam.setAllotStatus(1);
        scoreExExam.setLastTime(calculateLastTime(scoreExExam.getExStartDate(), scoreExExam.getExExamTimeList()));
        int i;
        if ((i = baseMapper.insertScoreExExam(scoreExExam)) > 0) {
            timeService.batchInsertScoreExExamTime(scoreExExam.getExExamTimeList(), scoreExExam.getId());
            placeService.batchInsertScoreExExamPlace(scoreExExam.getExExamPlaceList(), scoreExExam.getId());
        }
        return i;
    }

    /**
     * 修改考试
     *
     * @param scoreExExam 考试
     * @return 结果
     */
    @Override
    @Transactional
    public int updateScoreExExam(ScoreExExam scoreExExam) {
        checkExamDate(scoreExExam);
        ScoreExExam selectExam = baseMapper.selectScoreExExamById(scoreExExam.getId());
        if (DateTime.now().isAfter(selectExam.getLastTime())) {//如果当前时间在最后修改时间之后，无法修改
            return -1;
        }
        //重新计算最后修改时间
        scoreExExam.setLastTime(calculateLastTime(scoreExExam.getExStartDate(), scoreExExam.getExExamTimeList()));
        timeService.deleteScoreExExamTimeByExamId(scoreExExam.getId());
        timeService.batchInsertScoreExExamTime(scoreExExam.getExExamTimeList(), scoreExExam.getId());
        placeService.deleteScoreExExamPlaceByExamId(scoreExExam.getId());
        placeService.batchInsertScoreExExamPlace(scoreExExam.getExExamPlaceList(), scoreExExam.getId());
        return baseMapper.updateScoreExExam(scoreExExam);
    }

    /**
     * 计算最后可修改时间
     */
    private Date calculateLastTime(Date startDate, List<ScoreExExamTime> timeList) {
        //获取到最小的时间
        ScoreExExamTime examTime = timeList.stream().min(Comparator.comparing(time -> time.getStartTime().getTime())).get();
        //先合并，再减去两个小时
        return DateUtil.offset(DateUtil.dateJoinTime(startDate, examTime.getStartTime()), DateField.HOUR, -2);
    }

    /**
     * 检查考试考站在时段中是否有冲突，如果有，提示用户
     */
    private void checkExamDate(ScoreExExam scoreExExam) {
        Long examId = scoreExExam.getId();
        Date start = scoreExExam.getExStartDate();//开始时间
        Date end = scoreExExam.getExEndDate();//结束时间
        List<ScoreExExamTime> timeList = scoreExExam.getExExamTimeList();//时段
        List<ScoreExExamPlace> placeList = scoreExExam.getExExamPlaceList();//考站
        //考试时间
        List<DateTime> dateRange = DateUtil.rangeToList(start, end, DateField.DAY_OF_MONTH);
        StrBuilder result = new StrBuilder();
        int count = 0;
        for (DateTime day : dateRange) {
            for (ScoreExExamTime time : timeList) {
                for (ScoreExExamPlace place : placeList) {
                    String startTime = time.getStartTimeStr();
                    String endTime = time.getEndTimeStr();
                    List<Map<String, String>> occupy = baseMapper.selectScoreExExamOccupy(day, startTime, endTime, place.getPlaceId(), examId);
                    if (ListUtil.isNotEmpty(occupy)) {
                        for (Map<String, String> oc : occupy) {
                            result.append(StrUtil.format(CacheConstants.SCORE_EXAM_CHECK_HTML,
                                    day.toDateStr(), startTime, endTime,
                                    oc.get("placeName"), oc.get("exName"), oc.get("startTime"), oc.get("endTime")));
                            count++;
                        }
                    }
                }
            }
        }
        if (count > 0) {
            throw new GlobalException(555, result.toString());
        }
    }

    /**
     * 批量删除考试
     *
     * @param ids 需要删除的考试主键
     * @return 结果
     */
    @Override
    @Transactional
    public int deleteScoreExExamByIds(Long[] ids) {
        int count = 0;
        for (Long id : ids) {
            count += deleteScoreExExamById(id);
        }
        return count;
    }

    /**
     * 删除考试信息
     *
     * @param id 考试主键
     * @return 结果
     */
    @Override
    @Transactional
    public int deleteScoreExExamById(Long id) {
        ScoreExExam scoreExExam = selectScoreExExamById(id);
        //清除考试安排模型
        timeService.deleteScoreExExamTimeByExamId(id);
        placeService.deleteScoreExExamPlaceByExamId(id);
        //删除排站信息
        sessionService.deleteScoreExExamSessionByExamId(id);
        //清除学生信息
        studentService.deleteScoreExExamStudentByExamId(id);
        int i = baseMapper.deleteScoreExExamById(id);
        if (i > 0) {
            redisService.clearExamSocketModel(scoreExExam);
        }
        return i;
    }

    /**
     * 排站
     */
    @Override
    @Transactional
    public ScoreExExam allot(Long examId) {
        try {
            ScoreExExam scoreExExam = selectScoreExExamById(examId);
            if (ObjUtil.isNull(scoreExExam)) {
                throw new GlobalException("未查询到考试信息");
            }
            if (scoreExExam.getAllotStatus().equals(2)) {
                throw new GlobalException("已排站，请先清除排站信息");
            }
            /* 使用工具开始排站 */
            OsceAllotRequest request = new OsceAllotRequest(scoreExExam.getExStartDate(), scoreExExam.getExEndDate(),
                    scoreExExam.getMaxStudent(), scoreExExam.getExamIntervalTime(), "1".equals(scoreExExam.getAllotPlaceRule()));
            //添加房间
            for (ScoreExExamPlace place : scoreExExam.getExExamPlaceList()) {
                //查询考试时间
                ScoreSkill skill = skillService.selectScoreSkillById(place.getSkillId());
                request.addPlace(new PlaceModel(place.getId(), skill.getExDuration() / 60, place.getNum()));
            }
            //添加时间分段
            for (ScoreExExamTime time : scoreExExam.getExExamTimeList()) {
                request.addTime(new TimeModel(time.getId(), time.getStartTime(), time.getEndTime()));
            }
            OsceAllotResponse response = request.placeAllot();//执行排站算法
            List<ScoreExExamSession> sessions = ListUtil.linkedList();
            //将排站结果插入数据库
            response.forEach((exDate, placePeoPles) -> {
                placePeoPles.forEach((placeId, placeModels) -> {
                    for (int placeIndex = 0; placeIndex < placeModels.size(); placeIndex++) {
                        //创建考试节次
                        ScoreExExamSession session = ScoreExExamSession.builder().examId(scoreExExam.getId()).
                                examPlaceId(placeId).examPlaceIndex(placeIndex + 1).date(DateUtil.parseDate(exDate)).build();
                        //查询考站的评分表，随机插入学院安排
                        ScoreExExamPlace place = placeService.selectScoreExExamPlaceById(placeId);
                        List<Long> sheetIds = place.getSheetArr();
                        //创建一个存放时间分段下排站数据的map,创建节次下的排列数据
                        Map<Long, List<ScoreExExamPlaceAllot>> allotByTime = parseAllotByTime(scoreExExam.getExExamTimeList(), placeModels.get(placeIndex));
                        allotByTime.forEach((timeId, allots) -> {
                            if (ListUtil.isNotEmpty(allots)) {
                                //随机插入评分表
                                allots.forEach(a -> a.setSheetId(ListUtil.randomGet(sheetIds)));
                                ScoreExExamSession sessionCopy = BeanUtil.copyProperties(session, ScoreExExamSession.class);
                                sessionCopy.setTimeId(timeId);
                                sessionCopy.setScoreExExamPlaceAllotList(allots);
                                //存入数据
                                sessions.add(sessionCopy);
                            }
                        });
                    }
                });
            });
            sessionService.batchExExamSession(sessions);
            scoreExExam.setAllotStatus(2);
            baseMapper.updateScoreExExam(scoreExExam);
            //排站完成后，创建socket使用的考站模型
            redisService.buildExamSocketModel(examId);
            return scoreExExam;
        } catch (Exception e) {
            log.error(e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 清除排站
     */
    @Override
    @Transactional
    public ScoreExExam clearAllot(Long examId) {
        ScoreExExam scoreExExam = selectScoreExExamById(examId);
        if (ObjUtil.isNull(scoreExExam)) {
            throw new GlobalException("未查询到考试信息");
        }
        if (scoreExExam.getAllotStatus().equals(1)) {
            throw new GlobalException("未排站，请先排站");
        }
        //将排站节次已经数据清空
        sessionService.deleteScoreExExamSessionByExamId(scoreExExam.getId());
        scoreExExam.setAllotStatus(1);
        baseMapper.updateScoreExExam(scoreExExam);
        //清空安排学生
        placeAllotService.clearUserIdsByExam(examId);
        redisService.clearExamSocketModel(examId);
        return scoreExExam;
    }

    /**
     * 查询排站信息
     */
    @Override
    public ExamAllotResult selectAllot(Long examId) {
        if (selectScoreExExamById(examId).getAllotStatus().equals(1)) {
            throw new GlobalException("未排站");
        }
        //获取该考试已经安排的学生个数，@link {planStudent()}做了校验，所以不存在前端点击了安排学员没有一条的情况
        boolean isPlanStu = placeAllotService.planUserCountByExamId(examId) > 0;
        //查询该考场下的所有节次
        List<ScoreExExamSession> sessions = sessionService.selectScoreExExamSessionsByExamId(examId);
        //创建返回数据并根据日期分组
        return new ExamAllotResult(sessions, isPlanStu);
    }

    @Override
    public Map<String, Object> selectAllotAll(Long examId) {
        ExamAllotResult result = selectAllot(examId);
        return new HashMap<String, Object>() {{
            put("data", result);
            put("sessionMap", result.sessionMap());
            //根据ids获取时段map
            put("timeMap", timeService.selectScoreExExamTimeToMapByIds(result.timeIdArr()));
            //根据ids获取考站map,【key:ex_exam_place.id  ,value: ScoreExExamPlace】为了前端展示
            put("placeMap", placeService.selectBasicExPlaceToMapByIds(result.placeIdArr()));
        }};
    }


    /**
     * 安排学员
     *
     * @param id 考试编号
     */
    @Override
    @Transactional
    public void planStudent(Long id) {
        //查询该考试下已经添加的学员
        List<Long> checkIds = studentService.selectScoreExExamStudentCheckIds(id);
        if (ListUtil.isEmpty(checkIds)) {
            throw new GlobalException("未查询到学员，请先添加学员");
        }
        //清空userId
        placeAllotService.clearUserIdsByExam(id);
        //查询考站下的code
        List<String> codes = placeAllotService.selectCodeByExamIdGroupAndOrderByCode(id);
        if (ListUtil.isNotEmpty(codes)) {
            //打乱顺序
            checkIds = ListUtil.disrupt(checkIds);
            Iterator<String> iterator = codes.iterator();
            for (Long userId : checkIds) {
                if (iterator.hasNext()) {
                    placeAllotService.updateUserIdByExamIdAndCode(id, userId, iterator.next());
                }
            }
        }

    }

    @Override
    public void clearPlanStudent(Long id) {
        int i = placeAllotService.clearUserIdsByExam(id);
        if (i <= 0) {
            throw new GlobalException("未分配，无需清除");
        }
    }

    /**
     * 查询导出考生excel列表
     */
    @Override
    public List<ExamStudent> getExcelStudents(Long id) {
        List<ExamStudent> result = ListUtil.linkedList();
        //查询考试
        ScoreExExam exam = selectScoreExExamById(id);
        Map<Long, ScoreExExamPlace> placeMap = exam.getPlaceMap();
        Map<Long, ScoreExExamTime> timeMap = exam.getTimeMap();
        Map<Long, String> nickNameCache = MapUtil.hashMap();
        //查询该考场下的所有节次
        List<ScoreExExamSession> sessions = sessionService.selectScoreExExamSessionsByExamId(id);
        for (ScoreExExamSession session : sessions) {
            String date = session.getDateStr();
            ScoreExExamPlace place = placeMap.get(session.getExamPlaceId());
            String placeName = basicExPlaceService.selectBasicExPlaceById(place.getPlaceId()).getName() + '-' + session.getExamPlaceIndex();//场地名称
            String timeName = timeMap.get(session.getTimeId()).getName();//时间段名称
            String skillName = skillService.selectScoreSkillById(place.getSkillId()).getName();//技能名称
            for (ScoreExExamPlaceAllot allot : session.getScoreExExamPlaceAllotList()) {//遍历安排
                if (ObjUtil.isNotEmpty(allot.getUserId())) {
                    result.add(
                            new ExamStudent(exam.getName(), date, timeName, skillName, placeName, allot.getStartTimeStr(), allot.getEndTimeStr(), allot.getCode(),
                                    getAndCacheNickName(nickNameCache, allot.getUserId()))
                    );
                }
            }
        }
        return result;
    }

    /**
     * 查询导出考官excel列表
     */
    @Override
    public List<ExamExaminer> getExcelExaminers(Long id) {
        //查询考试
        ScoreExExam exam = selectScoreExExamById(id);
        Map<Long, ScoreExExamPlace> placeMap = exam.getPlaceMap();
        Map<Long, ScoreExExamTime> timeMap = exam.getTimeMap();
        Map<Long, String> nickNameCache = MapUtil.hashMap();
        //查询该考场下的所有节次
        List<ScoreExExamSession> sessions = sessionService.selectScoreExExamSessionsByExamId(id);
        return sessions.stream().map(session -> {
            String date = session.getDateStr();
            ScoreExExamPlace place = placeMap.get(session.getExamPlaceId());
            String placeName = basicExPlaceService.selectBasicExPlaceById(place.getPlaceId()).getName() + '-' + session.getExamPlaceIndex();//场地名称
            ScoreExExamTime time = timeMap.get(session.getTimeId());
            String timeName = time.getName();//时间段名称
            String skillName = skillService.selectScoreSkillById(place.getSkillId()).getName();//技能名称
            //String sheetName = sheetService.selectScoreSheetById(place.getSheetId()).getName();//评分表名称
            String scene = getAndCacheNickName(nickNameCache, session.getSceneExaminer());//现场考官
            String center = getAndCacheNickName(nickNameCache, session.getCenterExaminer());//中控考官
            String ssh = getAndCacheNickName(nickNameCache, session.getSshExaminer());//远程考官
            return new ExamExaminer(exam.getName(), date, timeName, skillName, placeName, time.getStartTimeStr(), time.getEndTimeStr(), scene, center, ssh);
        }).collect(Collectors.toList());
    }

    /**
     * 查询导出标准化病人excel列表
     */
    @Override
    public List<ExamSimulatedPatient> getExcelSimulatedPatients(Long id) {
        //查询考试
        ScoreExExam exam = selectScoreExExamById(id);
        Map<Long, ScoreExExamPlace> placeMap = exam.getPlaceMap();
        Map<Long, ScoreExExamTime> timeMap = exam.getTimeMap();
        Map<Long, String> nickNameCache = MapUtil.hashMap();
        //查询该考场下的所有节次
        List<ScoreExExamSession> sessions = sessionService.selectScoreExExamSessionsByExamId(id);
        return sessions.stream().map(session -> {
            String date = session.getDateStr();
            ScoreExExamPlace place = placeMap.get(session.getExamPlaceId());
            String placeName = basicExPlaceService.selectBasicExPlaceById(place.getPlaceId()).getName() + '-' + session.getExamPlaceIndex();//场地名称
            ScoreExExamTime time = timeMap.get(session.getTimeId());
            String timeName = time.getName();//时间段名称
            String skillName = skillService.selectScoreSkillById(place.getSkillId()).getName();//技能名称
            //String sheetName = sheetService.selectScoreSheetById(place.getSheetId()).getName();//评分表名称
            String simulatedPatient = getAndCacheNickName(nickNameCache, session.getSimulatedPatient());//标准化病人
            return new ExamSimulatedPatient(exam.getName(), date, timeName, skillName, placeName, time.getStartTimeStr(), time.getEndTimeStr(), simulatedPatient);
        }).collect(Collectors.toList());
    }

    /**
     * 导出考试下的所有信息
     * |---------------------------|
     * |         |     |  data[1]| |
     * |         |time1|---------- |
     * |         |     |  data[2]| |
     * | dateStr |---------------- |
     * |         |     |  data[4]| |
     * |         |time2|---------- |
     * |         |     |  data[5]| |
     * |---------------------------|
     */
    @Override
    public void exportExcelAllInfos(Long id, OutputStream out) throws IOException {
        //查询考试信息
        ScoreExExam exam = selectScoreExExamById(id);
        //获取该考试已经安排的学生个数，@link {planStudent}做了校验，所以不存在前端点击了安排学员没有一条的情况
        boolean isPlanStu = placeAllotService.planUserCountByExamId(id) > 0;
        //查询该考场下的所有节次
        List<ScoreExExamSession> sessions = sessionService.selectScoreExExamSessionsByExamId(id);
        //创建返回对戏那个并根据日期分组
        ExamAllotResult result = new ExamAllotResult(sessions, isPlanStu);
        //根据ids获取时段map
        Map<Long, ScoreExExamTime> timeView = timeService.selectScoreExExamTimeToMapByIds(result.timeIdArr());
        //根据ids获取考站map,【key:ex_exam_place.id  ,value: ScoreExExamPlace】为了前端展示
        Map<Long, ScoreExExamPlace> placesView = placeService.selectBasicExPlaceToMapByIds(result.placeIdArr());
        Map<String, ScoreExExamSession> sessionMap = result.sessionMap();
        Map<Long, String> nickNameCache = MapUtil.hashMap();
        JSONArray dates = JSONUtil.createArray();
        result.forEach((date, timeMap) -> {//数据处理
            JSONObject dateJson = new JSONObject();
            AtomicInteger timeSize = new AtomicInteger(0);//一天的长度，用于合并单元格
            JSONArray times = new JSONArray();
            timeMap.forEach((timeId, placeMap) -> {
                ScoreExExamTime examTime = timeView.get(timeId);
                JSONObject time = new JSONObject().set("name", examTime.getName()).set("start", examTime.getStartTimeStr()).set("end", examTime.getEndTimeStr());
                JSONArray datas = new JSONArray();
                placeMap.forEach((placeId, placeList) -> {
                    ScoreExExamPlace place = placesView.get(placeId);
                    for (int i = 1; i <= placeList.size(); i++) {
                        List<ScoreExExamPlaceAllot> placeAllots = placeList.get(i - 1);
                        timeSize.addAndGet(placeAllots.size());
                        ScoreExExamSession session = sessionMap.get(StrUtil.format("{}-{}-{}-{}", date, timeId, placeId, i));
                        String scene = getAndCacheNickName(nickNameCache, session.getSceneExaminer());
                        String center = getAndCacheNickName(nickNameCache, session.getCenterExaminer());
                        String ssh = getAndCacheNickName(nickNameCache, session.getSshExaminer());
                        String simulatedPatient = getAndCacheNickName(nickNameCache, session.getSimulatedPatient());
                        Map<Long, ScoreSheet> sheetMap = sheetService.selectSheetMapBySkillId(place.getSkillId());
                        String placeName = place.getPlaceName() + "-" + i;
                        for (ScoreExExamPlaceAllot allot : placeAllots) {
                            datas.add(new JSONObject().set("code", allot.getCode()).
                                    set("nickName", allot.getUserName()).set("timeRange", allot.getStartTimeStr() + "-" + allot.getEndTimeStr()).
                                    set("place", placeName).set("skill", place.getSkillName()).set("sheet", Option.of(sheetMap.get(allot.getSheetId())).get(new ScoreSheet()).getName()).
                                    set("scene", scene).set("center", center).set("ssh", ssh).set("simulatedPatient", simulatedPatient));
                        }
                    }
                });
                if (ListUtil.isNotEmpty(datas)) {
                    //排序
                    datas.sort((o1, o2) -> {
                        JSONObject o1Json = (JSONObject) o1;
                        JSONObject o2Json = (JSONObject) o2;
                        DateTime o1Time = DateTime.of(o1Json.getStr("timeRange").split("-")[0], "HH:mm:ss");
                        DateTime o2Time = DateTime.of(o2Json.getStr("timeRange").split("-")[0], "HH:mm:ss");
                        return o1Time.equals(o2Time) ? 0 : o1Time.after(o2Time) ? 1 : -1;
                    });
                    time.set("datas", datas);
                    times.add(time);
                }
            });
            if (ListUtil.isNotEmpty(times)) {
                dateJson.set("dateStr", date);
                dateJson.set("times", times);
                dateJson.set("times_size", timeSize.get() == 0 ? timeMap.size() : timeSize.get());
                dates.add(dateJson);
            }
        });
        Map<String, Object> models = new JSONObject().set("exam", new JSONObject() {{
            set("examName", exam.getName());
            set("examDateRange", exam.dateRangeStr());
            set("sceneExaminerWi", exam.getSceneExaminerWi());
            set("centerExaminerWi", exam.getCenterExaminerWi());
            set("sshExaminerWi", exam.getSshExaminerWi());
            set("examAllotRule", DictUtils.getDictLabel("score_ex_model_allot_place_rule_dict", exam.getAllotPlaceRule()));
            set("intervalTime", exam.getExamIntervalTime());
            set("studentCount", exam.getMaxStudent());
        }}).set("dates", dates);
        //创建一个临时文件
        String fileName = appValues.temporaryPath + UUIDUtil.simpleUUID() + ".xlsx";
        JxlsExcel.exportExcel("exam_mark_score_template.xlsx", new File(fileName), models);
        Map<String, List<?>> dataMap = new HashMap<String, List<?>>() {{
            put("学员数据", getExcelStudents(id));
            put("考官数据", getExcelExaminers(id));
            put("SP病人数据", getExcelSimulatedPatients(id));
        }};
        Map<String, String> aliaMap = new LinkedHashMap<String, String>() {{
            put("exName", "考试");
            put("date", "日期");
            put("time", "时间段");
            put("skill", "技能");
            put("sheet", "评分表");
            put("place", "场地");
            put("start", "开始时间");
            put("end", "结束时间");
            put("code", "代号");
            put("user", "考生名称");
            put("scene", "现场考官");
            put("center", "中控考官");
            put("ssh", "远程考官");
            put("patientUser", "病人");
        }};
        ExcelWriter writer = HutoExcelUtil.newWriter(fileName);
        //首行背景颜色
        CellStyle headCellStyle = writer.getHeadCellStyle();
        headCellStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
        headCellStyle.setFillBackgroundColor(IndexedColors.LIGHT_GREEN.getIndex());
        //首行字体
        Font font = writer.createFont();
        font.setBold(true);
        headCellStyle.setFont(font);
        HutoExcelUtil.writerAll(writer, dataMap, aliaMap);//写入
        HutoExcelUtil.export(writer, out, true);//导出
        FileUtil.del(fileName);//删除临时文件
    }

    @Override
    public List<ScoreExExam> selectScoreExExamTodayList() {
        ScoreExExam scoreExExam = new ScoreExExam();
        scoreExExam.addParams("exDate", DateTime.now().toDateStr());
        return baseMapper.selectScoreExExamList(scoreExExam);
    }

    /**
     * 检查考试的开考状态
     */
    public ScoreExExam checkExamStatusByDate(ScoreExExam exam) {
        if (exam.getStatus().equals(1) && DateTime.now().isAfter(exam.getLastTime())) {//如果当前时间在开考时间后，修改开考状态
            exam.setStatus(2);
            baseMapper.updateScoreExExam(exam);
        }
        return exam;
    }

    @Override
    public Map<String, Object> getExamTodayDateById(Long id) {
        Map<String, Object> allot = selectAllotAll(id);
        ExamAllotResult result = (ExamAllotResult) allot.get("data");
        return MapUtil.put(allot, "data", result.get(DateTime.now().toDateStr()));
    }

    /**
     * 检查用户是否在该考试下
     */
    @Override
    public void checkIsInExamByUser(Long examId, Long userId) {
        if (studentService.selectScoreExExamStudentList(ScoreExExamStudent.builder().examId(examId).userId(userId).build()).size() <= 0) {
            throw new GlobalException("未查询到该学员的相关考试信息");
        }
    }

    /**
     * 根据考试编号获取统计数据
     *
     * @param examId 考试编号
     */
    @Override
    public Map<String, Object> selectStatistics(Long examId) {
        /* 根据评分表分类成绩区间 */
        ScoreExExam scoreExExam = baseMapper.selectScoreExExamById(examId);
        Set<Long> sheetIds = SetUtil.hashSet();
        List<ScoreExExamPlace> placeList = scoreExExam.getExExamPlaceList();
        for (ScoreExExamPlace place : placeList) {
            sheetIds.addAll(place.getSheetArr());
        }
        Map<Long, ScoreSheet> sheetMap = sheetService.selectScoreSheetMapByIds(sheetIds);
        Stream<String> sheetNames = sheetMap.values().stream().map(ScoreSheet::getName);//前端需要使用的 legend


        return new HashMap<String, Object>() {{
            //折线图
            put("lineChartOption", JSONUtil.createObj().set("data", null));
            //柱状图
            put("histogramOption", null);
            //饼图
            put("pieChartOption", null);
            put("sheetNames", sheetNames);
        }};
    }


    /**
     * 判断一个存放时间分段下排站数据的map
     */
    private Map<Long, List<ScoreExExamPlaceAllot>> parseAllotByTime(List<ScoreExExamTime> times, PlaceModel placeModel) {
        Map<Long, List<ScoreExExamPlaceAllot>> result = MapUtil.map(true);
        for (ScoreExExamTime time : times) {
            Long timeId = time.getId();
            Date startTime = time.getStartTime();
            Date endTime = time.getEndTime();
            List<ScoreExExamPlaceAllot> placeAllots = ListUtil.list(true);
            for (PlaceAllotBean allotPoePle : placeModel.getPlaceAllotList()) {
                if (DateUtil.isIn(allotPoePle.getStartTime(), startTime, endTime)) {
                    placeAllots.add(
                            ScoreExExamPlaceAllot.builder().code(allotPoePle.getCode()).
                                    startTime(allotPoePle.getStartTime()).endTime(allotPoePle.getEndTime()).build());
                }
            }
            result.put(timeId, placeAllots);
        }
        return result;
    }

    /**
     * 为解决相同用户重复查询，将用户id和用户姓名临时缓存起来，生命周期为调用者的生命周期
     *
     * @param cacheMap 调用者创建的map
     * @param userId   用户编号
     * @return 姓名
     */
    private String getAndCacheNickName(Map<Long, String> cacheMap, Long userId) {
        return MapUtil.getAndCache(cacheMap, userId, () -> userService.selectNickNameByUserId(userId));
    }

}



















